package com.ds.msgbus;

import android.os.Handler;
import android.os.Looper;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;

public class MsgBus {
    private static MsgBus instance = new MsgBus();
    private  MsgBus(){

    }
    public static MsgBus getDefault(){
        return instance;
    }

    //储存EventType 及对应的Target 如:Activity 或者Service等
    private Map<Class<?>, List<Object>>eventTypeTargets = new ConcurrentHashMap<>();

    //目标及对应添加MsgSubscribe注解并且是public的方法
    private Map<Object,List<Method>>targetMethods=new ConcurrentHashMap<>();

    //target目标
    //注册成为订阅者
    //反射获取target类型 然后找有MsgSubscribe 把它缓存起来后续使用
    public void register(Object target){
        Class<?> aClass = target.getClass();
        //找到所有方法
        Method[] declaredMethods = aClass.getDeclaredMethods();
        for (Method method :
              declaredMethods  ) {
            //要求方法必须是public
            //getModifiers 返回修饰符的整型标识
             if (method.getModifiers() != Modifier.PUBLIC){
                continue;
            }
            //必须方法有MsgSubscribe注解  continue返回
            MsgSubscribe annotation = method.getAnnotation(MsgSubscribe.class);
            if (annotation == null) continue;
            //获取注解中标注的线程模型
            ThreadMode threadMode = annotation.threadMode();
            //event type事件类型
            //获取方法的第一个参数也就是eventType,后续按eventType发送消息给订阅者,因为订阅者可能有多个
            //getParameterTypes()获取所有参数类型
            Class<?>[] parameterTypes = method.getParameterTypes();
            //如果不等于 1返回
            if (parameterTypes.length != 1){
                continue;
            }
            //获取参数类型及时间类型(也就是方法的第一个参数)
            Class<?> parameterType = parameterTypes[0];
            //添加目标方法
            addTargetMethod(target,method);
            //添加时间类型到目标
            addEventTypeTarget(parameterType,target);

        }

    }

    private void addEventTypeTarget(Class<?> parameterType, Object target) {
        //判断如果这个方法存在直接返回
        if (eventTypeTargets.containsKey(parameterType) && eventTypeTargets.containsValue(target)){
            return;
        }
        //判断是否包含这个方法并添加到集合
        if (eventTypeTargets.containsKey(parameterType)){
            List<Object> objects = eventTypeTargets.get(parameterType);
            if (objects == null){
                objects = new ArrayList<>();
            }
            objects.add(target);
        }else {
            ArrayList<Object>objects = new ArrayList<>();
            objects.add(target);
            eventTypeTargets.put(parameterType,objects);
        }
    }

    private void addTargetMethod(Object target, Method method) {
        //判断包含目标方法
        if (targetMethods.containsKey(target)){
            //反射拿到这个方法添加到集合中
            List<Method> methods = targetMethods.get(target);
            methods.add(method);
        }else {
            List<Method>methods = new ArrayList<>();
            methods.add(method);
            targetMethods.put(target,methods);
        }
    }
    //发布消息
    public void post(Object msg){
        //获取到发布消息类
        Class<?> eventTypeClazz  = msg.getClass();
        for (Map.Entry<Class<?>,List<Object>>entry:
             eventTypeTargets.entrySet()) {
            if (entry.getKey() == eventTypeClazz ){
                List<Object> targets = entry.getValue();
                if (targets != null && targets.size()>0){
                    for (Object target:targets
                         ) {
                        List<Method> methods = targetMethods.get(target);
                        if (methods != null && methods.size()>0){
                            for (Method method:methods){
                                MsgSubscribe annotation = method.getAnnotation(MsgSubscribe.class);
                                if (annotation != null){
                                    switch (annotation.threadMode()){
                                        case POSTINNG:
                                            invokeMethod(method,target,msg);
                                            break;
                                        case MAIN:
                                            if (isMainThread()) {
                                                invokeMethod(method,target,msg);
                                            }else {
                                                mainHandler.post(new Runnable() {
                                                    @Override
                                                    public void run() {
                                                        invokeMethod(method,target,msg);
                                                    }
                                                });
                                            }
                                            break;
                                        case BACKGROUND:
                                            if (isMainThread()){
                                                executeAsync(new Runnable() {
                                                    @Override
                                                    public void run() {
                                                        invokeMethod(method,target,msg);
                                                    }
                                                });
                                            }
                                            break;
                                        case ASYNC:
                                            executeAsync(new Runnable() {
                                                @Override
                                                public void run() {
                                                    invokeMethod(method,target,msg);
                                                }
                                            });
                                            break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private boolean isMainThread() {
        //判断线程是否等于主线程
        if (Looper.getMainLooper().getThread()== Thread.currentThread()){
            return true;
        }
        return false;
    }

    private void invokeMethod(Method method, Object target, Object msg) {
        try {
            method.invoke(target,msg);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    //切换到主线程
   private Handler mainHandler = new Handler(Looper.getMainLooper());

    //用线程池执行
    private void executeAsync(Runnable runnable){
        Executors.newCachedThreadPool().submit(runnable);
    }


}
